#import <stdio.h>
#import <string.h>
#import <jinclude.h>
#import <streams/streams.h>
#import <appkit/graphics.h>
#import <appkit/NXBitmapImageRep.h>
#import <setjmp.h>
#import "NXBitmapImageRepControl.h"
#import "ImageControl.h"
#import "jpeg.h"

@implementation JPEG

/* These static variables are needed by the error routines. */
static jmp_buf 			setjmp_buffer;	// for return to caller
static external_methods_ptr	emethods;		// needed for access to message_parm

static NXStream	*inStream;
static NXStream	*outStream;

static JSAMPARRAY 		color_map;		/* saves color map passed by quantizer */
static NXBitmapImageRep	*outputImage;
static NXBitmapImageRep	*inputImage;
static ImageControl		*controls;
static GetPixelNextFunc	nextPixelFunc;
static ToGrayFunc			toGrayFunc;
static unsigned char 		*outData[5];
static long 				offset = 0;
static int					curError;
static int 				errState;

METHODDEF void trace_message (const char *msgtext)
{
	fprintf(stderr, msgtext,
		emethods->message_parm[0], emethods->message_parm[1],
		emethods->message_parm[2], emethods->message_parm[3],
		emethods->message_parm[4], emethods->message_parm[5],
		emethods->message_parm[6], emethods->message_parm[7]);
	fprintf(stderr, "\n");   /* there is no \n in the format string! */
}

METHODDEF void error_exit (const char *msgtext)
{
	trace_message(msgtext);   /* report the error message */
	(*emethods->free_all) ();   /* clean up memory allocation & temp files */
	longjmp(setjmp_buffer, 1);   /* return control to outer routine */
}

METHODDEF void output_init (decompress_info_ptr cinfo)
{
	int				spp = 1;
	NXColorSpace	cs = NX_OneIsWhiteColorSpace;
	
	if (cinfo->out_color_space == CS_GRAYSCALE) {
		spp = 1;
		cs = NX_OneIsWhiteColorSpace;
	} else if (cinfo->out_color_space == CS_RGB) {
		spp = 3;
		cs = NX_RGBColorSpace;
	} else if (cinfo->out_color_space == CS_CMYK) {
		spp = 4;
		cs = NX_CMYKColorSpace;
	}

	outputImage = [NXBitmapImageRep alloc];
	[outputImage 	initData:			NULL
				pixelsWide:		cinfo->image_width
				pixelsHigh:		cinfo->image_height 
				bitsPerSample:	8
				samplesPerPixel:	spp
				hasAlpha:		NO
				isPlanar:			YES
				colorSpace:		cs
				bytesPerRow:		0
				bitsPerPixel:		0];
	[outputImage getDataPlanes: outData];
}

METHODDEF void put_pixel_rows (decompress_info_ptr cinfo, int num_rows, JSAMPIMAGE pixel_data)
{
	register long width = cinfo->image_width;
	register int row;

	if (cinfo->out_color_space == CS_GRAYSCALE) {
		for (row = 0; row < num_rows; row++) {
			bcopy(pixel_data[0][row], outData[0] + offset, width);
			offset += width;
		}
	} else if (cinfo->out_color_space == CS_RGB) {
		for (row = 0; row < num_rows; row++) {
			bcopy(pixel_data[0][row], outData[0] + offset, width);
			bcopy(pixel_data[1][row], outData[1] + offset, width);
			bcopy(pixel_data[2][row], outData[2] + offset, width);
			offset += width;
		}
	} else if (cinfo->out_color_space == CS_CMYK) {
		for (row = 0; row < num_rows; row++) {
			bcopy(pixel_data[0][row], outData[0] + offset, width);
			bcopy(pixel_data[1][row], outData[1] + offset, width);
			bcopy(pixel_data[2][row], outData[2] + offset, width);
			bcopy(pixel_data[3][row], outData[3] + offset, width);
			offset += width;
		}
	}
}

METHODDEF void put_color_map (decompress_info_ptr cinfo, int num_colors, JSAMPARRAY colormap)
{
	color_map = colormap;					/* save for use in output */
#ifdef DEBUG
	fprintf(stderr, "This shouldn't get  called\n");
#endif
//	cinfo->methods->put_pixel_rows = put_demapped_rows;
}

METHODDEF void output_term (decompress_info_ptr cinfo)
{
#ifdef DEBUG
	fprintf(stderr, "Image was sucessfully read\n");
#endif
	errState = CONVERT_ERR_NONE;
	curError = ERROR_NO_ERROR;
}

/*
 * This routine gets control after the input file header has been read.
 * It must determine what output file format is to be written,
 * and make any other decompression parameter changes that are desirable.
 */

METHODDEF void d_ui_method_selection (decompress_info_ptr cinfo)
{
#ifdef DEBUG
	fprintf(stderr, "Output format select\n");
#endif
	cinfo->methods->output_init = output_init;
	cinfo->methods->put_color_map = put_color_map;
	cinfo->methods->put_pixel_rows = put_pixel_rows;
	cinfo->methods->output_term = output_term;
}


/*
 * Reload the input buffer after it's been emptied, and return the next byte.
 * See the JGETC macro for calling conditions.
 *
 * This routine would need to be replaced if reading JPEG data from something
 * other than a stdio stream.
 */

METHODDEF void progress_monitor (decompress_info_ptr cinfo, long loopcounter, long looplimit)
{
	fprintf(stderr, "%d%%...", (int)((float)loopcounter / (float)looplimit * 100.0));
}

METHODDEF int read_jpeg_data (decompress_info_ptr cinfo)
{
	cinfo->next_input_byte = cinfo->input_buffer + MIN_UNGET;
	cinfo->bytes_in_buffer = NXRead(	inStream, 
									cinfo->next_input_byte, 
									JPEG_BUF_SIZE);

	if (cinfo->bytes_in_buffer <= 0) {
		curError = ERROR_TRUNCATED_FILE;
		errState = CONVERT_ERR_WARNING;
		ERREXIT(cinfo->emethods, "Unexpected EOF in JPEG file");
	}

	return JGETC(cinfo);
}

- init
{
	return self;
}

- free
{
	return [super free];
}

- readFromStream: (NXStream *)stream from: sender;
{
	struct decompress_info_struct		cinfo;
	struct decompress_methods_struct	dc_methods;
	struct external_methods_struct		e_methods;

	curError = ERROR_NO_ERROR;
	errState = CONVERT_ERR_NONE;

	/* Initialize the system-dependent method pointers. */
	cinfo.methods = &dc_methods;
	cinfo.emethods = &e_methods;

	emethods = &e_methods;				// save struct addr for possible access
	e_methods.error_exit = error_exit;		// supply error-exit routine
	e_methods.trace_message = trace_message; // supply trace-message routine

	/* prepare setjmp context for possible exit from error_exit */
	if (setjmp(setjmp_buffer)) {
		/* If we get here, the JPEG code has signaled an error.
		 * Memory allocation has already been cleaned up (see free_all call in
		 * error_exit), but we need to close the input file before returning.
		 * You might also need to close an output file, etc.
		 */
		 fprintf(stderr, "Bummer, failing\n");
		NXClose(outStream);
		NXClose(inStream);
		if (errState != CONVERT_ERR_NONE) {
			curError = ERROR_UNABLE_TO_OPEN;
			errState = CONVERT_ERR_FATAL;
		}
		return outputImage;
	}
	
	jselmemmgr(&e_methods);				/* memory allocation routines */
	dc_methods.d_ui_method_selection = d_ui_method_selection;

	j_d_defaults(&cinfo, TRUE);

	dc_methods.read_jpeg_data = read_jpeg_data;
	dc_methods. progress_monitor = progress_monitor;

	/* Set up default input and output file references. */
	/* (These may be overridden below.) */
	inStream = stream;
	cinfo.output_file = stderr;
	outStream = NXOpenFile(fileno(stdout), NX_WRITEONLY);

	/* Set up default parameters. */
	e_methods.trace_level 			= 0;
	cinfo.output_gamma 			= 1.0;
	cinfo.quantize_colors 			= FALSE;
	cinfo.two_pass_quantize 		= FALSE;
	cinfo.use_dithering 			= FALSE;
	cinfo.desired_number_of_colors	= 256;
	cinfo.do_block_smoothing 		= FALSE;
	cinfo.do_pixel_smoothing 		= FALSE;
//	cinfo.out_color_space 			= CS_RGB;
	cinfo.jpeg_color_space 			= CS_UNKNOWN;
	/* setting any other value in jpeg_color_space overrides heuristics */
	/* in jrdjfif.c ... */
	/* You may wanta change the default output format; here's the place: */
	cinfo.do_block_smoothing 		= FALSE;

	offset = 0;

	jselrjfif(&cinfo);
	
	/* Do it to it! */
	jpeg_decompress(&cinfo);

	/* Release memory. */

	NXClose(outStream);
	NXClose(inStream);

	return outputImage;
}

METHODDEF void progress_monitorII(compress_info_ptr cinfo, long loopcounter, long looplimit)
{
	fprintf(stderr, "%d%%...", (int)((float)loopcounter / (float)looplimit * 100.0));
}

METHODDEF void input_init (compress_info_ptr cinfo)
/* Initialize for input; return image size and component data. */
{
	/* This routine must return five pieces of information about the incoming
	 * image, and must do any setup needed for the get_input_row routine.
	 * The image information is returned in fields of the cinfo struct.
	 * (If you don't care about modularity, you could initialize these fields
	 * in the main JPEG calling routine, and make this routine be a no-op.)
	 * We show some example values here.
	 */
	cinfo->image_width = [inputImage pixelsWide];   /* width in pixels */
	cinfo->image_height = [inputImage pixelsHigh];   /* width in pixels */
	/* JPEG views an image as being a rectangular array of pixels, with each
	 * pixel having the same number of "component" values (color channels).
	 * You must specify how many components there are and the colorspace
	 * interpretation of the components.  Most applications will use RGB data or
	 * grayscale data.  If you want to use something else, you'll need to study
	 * and perhaps modify jcdeflts.c, jccolor.c, and jdcolor.c.
	 */
	switch ([inputImage colorSpace]) {
  	 	case NX_OneIsWhiteColorSpace:
			cinfo->input_components = 1;
			cinfo->in_color_space = CS_GRAYSCALE;
			cinfo->data_precision = 8;
			break;
		case NX_OneIsBlackColorSpace:
			cinfo->input_components = 1;
			cinfo->in_color_space = CS_GRAYSCALE;
			cinfo->data_precision = 8;
			break;
		case NX_RGBColorSpace:
			cinfo->input_components = 3;
			cinfo->in_color_space = CS_RGB;
			cinfo->data_precision = 8;
			break;
		case NX_CMYKColorSpace:
			cinfo->input_components = 4;
			cinfo->in_color_space = CS_CMYK;
			cinfo->data_precision = 8;
			break;
		default:
			fprintf(stderr, "Can't deal with this!\n");
	}
	fprintf(stderr, "%d %d %d\n", cinfo->input_components, cinfo->in_color_space, cinfo->data_precision);
}

METHODDEF void
get_input_row (compress_info_ptr cinfo, JSAMPARRAY pixel_row)
/* Read next row of pixels into pixel_row[][] */
{
	/* This example shows how you might read RGB data (3 components)
	 * from an input file in which the data is stored 3 bytes per pixel
	 * in left-to-right, top-to-bottom order.
	 */
	register FILE			* infile = cinfo->input_file;
	register JSAMPROW	ptr0, ptr1, ptr2, ptr3;
	register long			col;
	register Pixel			*myPixel;

	switch ([inputImage colorSpace]) {
  	 	case NX_OneIsWhiteColorSpace:
			ptr0 = pixel_row[0];
			for (col = 0; col < cinfo->image_width; col++) {
				*ptr0++ = (JSAMPLE)(255 - nextPixelFunc()->values[0]);
			}
			break;
		case NX_OneIsBlackColorSpace:
			ptr0 = pixel_row[0];
			for (col = 0; col < cinfo->image_width; col++) {
				*ptr0++ = (JSAMPLE)(nextPixelFunc()->values[0]);
			}
			break;
		case NX_RGBColorSpace:
			ptr0 = pixel_row[0];
			ptr1 = pixel_row[1];
			ptr2 = pixel_row[2];
			for (col = 0; col < cinfo->image_width; col++) {
				myPixel = nextPixelFunc();
				*ptr0++ = (JSAMPLE)(myPixel->values[0]);
				*ptr1++ = (JSAMPLE)(myPixel->values[1]);
				*ptr2++ = (JSAMPLE)(myPixel->values[2]);
			}
			break;
		case NX_CMYKColorSpace:
			ptr0 = pixel_row[0];
			ptr1 = pixel_row[1];
			ptr2 = pixel_row[2];
			ptr3 = pixel_row[3];
			for (col = 0; col < cinfo->image_width; col++) {
				myPixel = nextPixelFunc();
				*ptr0++ = (JSAMPLE)(myPixel->values[0]);
				*ptr1++ = (JSAMPLE)(myPixel->values[1]);
				*ptr2++ = (JSAMPLE)(myPixel->values[2]);
				*ptr3++ = (JSAMPLE)(myPixel->values[3]);
			}
			break;
		default:
			fprintf(stderr, "Can't deal with this!\n");
	}
}

METHODDEF void input_term (compress_info_ptr cinfo)
/* Finish up at the end of the input */
{
	/* This termination routine will very often have no work to do, */
	/* but you must provide it anyway. */
	/* Note that the JPEG code will only call it during successful exit; */
	/* if you want it called during error exit, you gotta do that yourself. */
}

METHODDEF void c_ui_method_selection (compress_info_ptr cinfo)
{
	jselwjfif(cinfo);
}

- (BOOL)write: (id)image toStream: (NXStream *)stream from: sender;
{
	/* These three structs contain JPEG parameters and working data.
	 * They must survive for the duration of parameter setup and one
	 * call to jpeg_compress; typically, making them local data in the
	 * calling routine is the best strategy.
	 */
	struct compress_info_struct			cinfo;
	struct compress_methods_struct		c_methods;
	struct external_methods_struct		e_methods;

	inputImage = image;
	controls = [sender getImageControl: inputImage];
	nextPixelFunc = [controls getNextFunction];
	toGrayFunc = [controls getToGrayFunction];
	[controls resetNext];
	
	/* Initialize the system-dependent method pointers. */
	cinfo.methods = &c_methods;			/* links to method structs */
	cinfo.emethods = &e_methods;

	e_methods.error_exit = error_exit;		// supply error-exit routine

	/* prepare setjmp context for possible exit from error_exit */
	if (setjmp(setjmp_buffer)) {
		/* If we get here, the JPEG code has signaled an error.
		 * Memory allocation has already been cleaned up (see free_all call in
		 * error_exit), but we need to close the input file before returning.
		 * You might also need to close an output file, etc.
		 */
		 fprintf(stderr, "Bummer, failing\n");
		if (errState != CONVERT_ERR_NONE) {
			curError = ERROR_UNABLE_TO_WRITE;
			errState = CONVERT_ERR_FATAL;
		}
		return NO;
	}
	
	/* Here we use the default JPEG error handler, which will just print
	 * an error message on stderr and call exit().  See the second half of
	 * this file for an example of more graceful error recovery.
	 */
	jselerror(&e_methods);   /* select std error/trace message routines */
	/* Here we use the standard memory manager provided with the JPEG code.
	 * In some cases you might want to replace the memory manager, or at
	 * least the system-dependent part of it, with your own code.
	 */
	jselmemmgr(&e_methods);   /* select std memory allocation routines */
	/* If the compressor requires full-image buffers (for entropy-coding
	 * optimization or a noninterleaved JPEG file), it will create temporary
	 * files for anything that doesn't fit within the maximum-memory setting.
	 * (Note that temp files are NOT needed if you use the default parameters.)
	 * You can change the default maximum-memory setting by changing
	 * e_methods.max_memory_to_use after jselmemmgr returns.
	 * On some systems you may also need to set up a signal handler to
	 * ensure that temporary files are deleted if the program is interrupted.
	 * (This is most important if you are on MS-DOS and use the jmemdos.c
	 * memory manager back end; it will try to grab extended memory for
	 * temp files, and that space will NOT be freed automatically.)
	 * See jcmain.c or jdmain.c for an example signal handler.
	 */

	/* Here, set up pointers to your own routines for input data handling
	 * and post-init parameter selection.
	 */
	c_methods.input_init = input_init;
	c_methods.get_input_row = get_input_row;
	c_methods.input_term = input_term;
	c_methods.c_ui_method_selection = c_ui_method_selection;

	/* Set up default JPEG parameters in the cinfo data structure. */
	j_c_defaults(&cinfo, 75, FALSE);
	/* Note: 75 is the recommended default quality level; you may instead pass
	 * a user-specified quality level.  Be aware that values below 25 will cause
	 * non-baseline JPEG files to be created (and a warning message to that
	 * effect to be emitted on stderr).  This won't bother our decoder, but some
	 * commercial JPEG implementations may choke on non-baseline JPEG files.
	 * If you want to force baseline compatibility, pass TRUE instead of FALSE.
	 * (If non-baseline files are fine, but you could do without that warning
	 * message, set e_methods.trace_level to -1.)
	 */

	/* Select the input and output files.
	 * Note that cinfo.input_file is only used if your input reading routines
	 * use it; otherwise, you can just make it NULL.
	 * VERY IMPORTANT: use "b" option to fopen() if you are on a machine that
	 * requires it in order to write binary files.
	 */

	c_methods. progress_monitor = progress_monitorII;

	cinfo.input_file = NULL;   /* if no actual input file involved */

	cinfo.output_file = (FILE *)stream;

	/* Here we go! */
	jpeg_compress(&cinfo);

	/* That's it, son.  Nothin' else to do, except close files. */
	/* Here we assume only the output file need be closed. */
	NXFlush(((NXStream *)(cinfo.output_file)));

	/* Note: if you want to compress more than one image, we recommend you
	 * repeat this whole routine.  You MUST repeat the j_c_defaults()/alter
	 * parameters/jpeg_compress() sequence, as some data structures allocated
	 * in j_c_defaults are freed upon exit from jpeg_compress.
	 */
	[controls free];

	return YES;
}

- readAllFromStream: (NXStream *)stream from: sender
{
	return nil;
}

- (BOOL)writeAll: (id)image toStream: (NXStream *)stream
{
	return NO;
}

- customSaveView: (int)width
{
	return nil;
}

- customOpenView: (int)width
{
	return nil;
}

- (char *)getFormatName
{
	return("Stand Alone JPEG Compressed File (JFIF)");
}

 - (BOOL)setCustomParameter: (const char *)parameter withValue: (void *)ptr
{
	return NO;
}

 - (void *)getCustomParameter: (const char *)parameter
{
	return NULL;
}

- (char *)copyrightNotice
{
	return "JPEG Converter\nby Alex Raftis\n\nCopyright (c) 1991 Cal Poly State University\nCopyright (c) 1991 The Independent JPEG Group\n\nEmail bugs to alex@data.ACS.CalPoly.EDU";
}
 
- (int)errorState
{
	return errState;
}
 
- (int)errorMessage
{
	return curError;
}

- (char *)errorStringMessage
{
	return NULL;
}
 
- (BOOL)needsWindowServer;
{
	return NO;
}

- (char *)protocolVersion
{
	return "1.0";
}

@end
